﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Volo.Abp.DependencyInjection;
using Xms.Abp.BackgroundJob;
using Xms.Abp.Domain;
using Xms.Abp.Domain.Repositories;

namespace BackgroundJobManagement;

[Dependency(ReplaceServices = true)]
public class BackgroundJobStore : XmsDomainService, IBackgroundJobStore, ITransientDependency
{
    protected IXmsRepository<BackgroundJobEntity, Guid> Repository
        => LazyInject<IXmsRepository<BackgroundJobEntity, Guid>>();

    public virtual async Task<BackgroundJobInfo> FindAsync(Guid jobId, CancellationToken cancellationToken = default)
    {
        var entity = await Repository.FindAsync(jobId, cancellationToken: cancellationToken);
        return ObjectMapper.MapTo<BackgroundJobInfo>(entity);
    }

    public virtual async Task InsertAsync(BackgroundJobInfo jobInfo, CancellationToken cancellationToken = default)
    {
        var entity = ObjectMapper.MapTo<BackgroundJobEntity>(jobInfo);
        await Repository.InsertAsync(entity, true, cancellationToken);
    }

    public virtual async Task<List<BackgroundJobInfo>> GetWaitingJobsAsync(int maxResultCount, CancellationToken cancellationToken = default)
    {
        var now = Clock.Now;
        var list = await (await Repository.AsNoTrackingAsync())
            .Where(t => !t.IsAbandoned && (t.NextTryTime <= now || t.TryTimeoutTime <= now && t.BackgroundJobStatus == BackgroundJobStatus.Executing))
            .OrderByDescending(t => t.Priority)
            .ThenBy(t => t.TryCount)
            .ThenBy(t => t.NextTryTime)
            .Take(maxResultCount).ToListAsync(cancellationToken);
        return ObjectMapper.MapTo<BackgroundJobInfo>(list);
    }

    public virtual async Task DeleteAsync(Guid jobId, CancellationToken cancellationToken = default)
    {
        await Repository.DeleteAsync(jobId, true, cancellationToken);
    }

    public virtual async Task UpdateAsync(BackgroundJobInfo jobInfo, CancellationToken cancellationToken = default)
    {
        var backgroundJobRecord = await Repository.FindAsync(jobInfo.Id, cancellationToken: cancellationToken);
        if (backgroundJobRecord == null)
        {
            return;
        }

        ObjectMapper.Map(jobInfo, backgroundJobRecord);
        await Repository.UpdateAsync(backgroundJobRecord, true, cancellationToken);
    }

    public virtual async Task UpdateAsync(List<BackgroundJobInfo> jobInfos, CancellationToken cancellationToken = default)
    {
        var ids = jobInfos.Select(r => r.Id).ToList();
        var backgroundJobRecords = await (await Repository.AsNoTrackingAsync()).Where(r => ids.Contains(r.Id)).ToListAsync(cancellationToken);
        var dict = jobInfos.ToDictionary(r => r.Id);
        foreach (var backgroundJobRecord in backgroundJobRecords)
        {
            var jobInfo = dict[backgroundJobRecord.Id];
            ObjectMapper.Map(jobInfo, backgroundJobRecord);
        }
        await Repository.UpdateManyAsync(backgroundJobRecords, true, cancellationToken);
    }
}
